"""
Over the years I have been required to read/write Python data stuctures to xml.

Today it is perhaps better to use JSON data structures... it is more lightweigtht and readable.

This test excerpt demonstrates that the jsonpickle package provides a simple lightweight solution.

NOTE:
I have not bothered to write the encoded strings out to files.
I have not produced a unit test_.
I have not run coverage.
"""
import jsonpickle

def format(string):
    """
    Format JSON output for checking on the console.
    """
    indent = 0
    result = []
    for c in string:
        if c==',':
            result.append(c)
            result.append('\n')
            result.append(' '*indent)
        elif c in ('{','['):
            result.append (c)
            result.append('\n')
            indent += 3
            result.append(' '*indent)
        elif c in ('}', ']'):
            result.append('\n')
            indent -= 3
            result.append(' '*indent)
            result.append (c)
        else:
            result.append (c)
    return ''.join(result)
            
INIT = 'init'
REL = 'rel'

class _Resource(object):
    """
    A resource has a unique name and has a type and a default value.
    Underscore used to prevent accidental use.
    """
    db = {} # store all resources here
    @classmethod
    def encode(cls):
        return jsonpickle.encode(cls.db)
    @classmethod
    def decode(cls, encoded):
        cls.db = jsonpickle.decode(encoded)
    @classmethod
    def factory(cls, **k):
        """
        Resources should all be created using the factory so that  existing resources can be looked up
        and new ones created as appropriate.
        """
        assert 'name' in k, 'Resource must have name'
        name = k['name']
        if name in cls.db:
            assert len(k)==1, 'Resource lookup by name only'
            return cls.db[name]
        else:
            assert 'type_' in k, 'Resource must have type_'
            assert 'default' in k, 'Resource must have default'
        result = cls.db[name] = cls(**k)
        return result
    def __init__(self, **k):
        """
        Simply save all the keywords as attributes.
        """
        for key in k:
            setattr(self, key, k[key])
    
def Resource(**k):
    """
    A bit sneaky but the function looks to the user like a Resource constructor.
    This forces all Resources to be created via the factory.
    """
    return _Resource.factory(**k)
def resource_encode():
    return _Resource.encode() # convinience
def resource_decode(encoded):
    return _Resource.decode(encoded)

class Control(object):
    """
    A control refers to a resource and optionally provides a value.
    """
    def __init__(self, name, value=None):
        self.resource = Resource(name=name)
        if value is None:
            value = self.resource.default
        self.value = value
        
class Event(object):
    """
    An event can be one of several kinds, has a name and several Controls.
    """
    def __init__(self, kind, name):
        self.kind = kind
        self.name = name
        self.controls = []
    def append(self, name, value=None): # Add a control
        control = Control(name, value)
        self.controls.append(control)
        return control
        
class Exercise(object):
    """
    An exercise has a name, some resources and some Events.
    """
    def __init__(self, name):
        self.name = name
        self.resources = {}
        self.events = []
    def append(self, kind, name): # add an Event
        event = Event(kind, name)
        self.events.append(event)
        return event
    def set_resource(self, name, value): # A new or updated resource
        self.resources[name] = Resource(name=name), value
    def get_resource(self,name): # Read a resource value
        return self.resources[name]
    def del_resource(self, name): # Remove a resource
        del self.resources[name]
    def encode(self): # Produce the JSON from the current object
        return jsonpickle.encode(self)
    @classmethod # Read JSON and create a new object
    def decode(cls, encoded):
        return jsonpickle.decode(encoded)

if __name__ == '__main__':
    Resource(name='fault1', type_=int, min=0, max=1, default=0)
    Resource(name='fault2', type_=int, min=0, max=1, default=0)

    Resource(name='signal1', type_=float, min=100, max=100, default=0)

    Resource(name='option1', type_=str, values=('on', 'off'), default='on')

    ex = Exercise('latest')
    ex.set_resource('option1', 'on')
    ev = ex.append(INIT, 'base')
    ev.append('fault1')
    ev.append('fault2', 1)

    Exercise('blank')

    print format(resource_encode())
    resource_decode(resource_encode())
    print format(resource_encode())
    print ex
    print format(ex.encode())
    ex2 = ex.decode(ex.encode())
    print ex2
    print format(ex2.encode())

